package com.george.order.loadbalance;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @title: WeightedRoundRobinLoadBalancer.java
 * @description: 负载均衡算法- 加权轮询（Weighted Round Robin）
 * @author: George.Dong
 * @date: 2024/5/30 11:41
 */
public class WeightedRoundRobinLoadBalancer {
    /**
     * 服务器列表，包含每个服务器的权重信息
     */
    private List<WeightServer> servers;
    /**
     * 当前索引，用于追踪下一个要选择的服务器
     */
    private AtomicInteger currentIndex = new AtomicInteger(-1);
    /**
     * 当前累计的权重，用于选择下一个服务器
     */
    private int currentWeight = 0;
    /**
     * 所有服务器权重的最大数
     */
    private int maxWeight;
    /**
     * 所有服务器权重的最大公约数
     */
    private int gcdWeight;

    /**
     * 构造函数，初始化负载均衡器
     *
     * @param servers 服务器列表，每个服务器包含一个名称和权重
     */
    public WeightedRoundRobinLoadBalancer(List<WeightServer> servers) {
        this.servers = servers;
        this.maxWeight = getMaxWeight(servers);
        this.gcdWeight = getGcdWeight(servers);
    }

    /**
     * 获取下一个服务器
     * 此方法根据加权轮询算法选择下一个服务器，并同步处理多线程访问。
     *
     * @return {@link WeightServer} 返回下一个应服务的服务器。如果没有可用服务器，返回null。
     */
    public synchronized WeightServer getNextServer() {
        while (true) {
            // 循环选择下一个服务器
            int index = currentIndex.incrementAndGet() % servers.size();
            if (index == 0) {
                // 当选择到第一个服务器时，可能需要调整当前权重
                // 减去gcdWeight的目的是实现更公平的权重分配。gcdWeight是服务器列表中所有权重的最大公约数。
                // 在循环开始时，减去gcdWeight可以使currentWeight快速下降到一个较小的值，
                // 从而更快地遍历到权重较小的服务器。这样可以避免权重较大的服务器被连续选择，实现更均衡的负载分布
                currentWeight = currentWeight - gcdWeight;
                if (currentWeight <= 0) {
                    // 如果当前权重小于等于0，重新设置当前权重
                    currentWeight = maxWeight;
                    if (currentWeight == 0) {
                        // 如果最大权重为0，表示没有可用服务器
                        return null;
                    }
                }
            }
            if (servers.get(index).getWeight() >= currentWeight) {
                // 如果服务器权重大于等于当前权重，选择该服务器
                return servers.get(index);
            }
        }
    }

    /**
     * 计算服务器列表中所有权重的最大数
     *
     * @param servers 服务器列表
     * @return int
     */
    private int getMaxWeight(List<WeightServer> servers) {
        int maxWeight = 0;
        for (WeightServer server : servers) {
            if (server.getWeight() > maxWeight) {
                maxWeight = server.getWeight();
            }
        }
        return maxWeight;
    }

    /**
     * 计算服务器列表中所有权重的最大公约数
     * 其实就是算每次的步长
     * @param servers 服务器列表
     * @return int
     */
    private int getGcdWeight(List<WeightServer> servers) {
        int gcdWeight = 0;
        for (WeightServer server : servers) {
            gcdWeight = gcd(gcdWeight, server.getWeight());
        }
        return gcdWeight;
    }

    /**
     * 计算两个数的最大公约数
     *
     * @param a
     * @param b
     * @return int
     */
    private int gcd(int a, int b) {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }

    public static void main(String[] args) {
        // 初始化服务器列表，带权重配置
        List<WeightServer> servers = Arrays.asList(
                new WeightServer("Server1", 4),
                new WeightServer("Server2", 10),
                new WeightServer("Server3", 6)
                // [Server1，Server1，Server2，Server2，Server2，Server2，Server2，Server3，Server3，Server3]
        );
        // 创建负载均衡器实例
        WeightedRoundRobinLoadBalancer lb = new WeightedRoundRobinLoadBalancer(servers);

        // 模拟10个请求，并打印出处理每个请求的服务器名称
        for (int i = 0; i < 20; i++) {
            String clientIp = "192.168.0.13" + i;
            WeightServer server = lb.getNextServer();
            System.out.println("Client IP: " + clientIp + " -> Server: " + server.getName());
        }
    }
}

